Java IO学习-NIO 网络编程
非阻塞 vs 阻塞
阻塞模式
阻塞模式下,相关方法都会导致线程暂停
ServerSocketChannel.accept会在没有连接建立时让线程暂停SocketChannel.read会在没有数据可读时让线程暂停- 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
但多线程下,有新的问题,体现在以下方面
- 32 位 JVM 一个线程 320k,64 位 JVM 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低
- 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接
编写服务端代码:
public class NettyServer {
public static void main(String[] args) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(16);
// 获得服务器通道
try(ServerSocketChannel server = ServerSocketChannel.open()) {
// 为服务器通道绑定端口
server.bind(new InetSocketAddress(8080));
// 用户存放连接的集合
ArrayList<SocketChannel> channels = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
// 循环接收连接
while (true) {
System.out.println("before connecting...");
// 没有连接时,会阻塞线程
SocketChannel socketChannel = server.accept();
System.out.println("after connecting...");
channels.add(socketChannel);
// 循环遍历集合中的连接
for(SocketChannel channel : channels) {
System.out.println("before reading");
// 处理通道中的数据
// 当通道中没有数据可读时,会阻塞线程
channel.read(buffer);
buffer.flip();
// 这里使用之前的 debug 工具
ByteBufferUtil.debugRead(buffer);
// 发送内容
String next = scanner.next();
ByteBuffer byteBuffer = ByteBuffer.wrap(next.getBytes());
while (byteBuffer.hasRemaining()) {
channel.write(byteBuffer);
}
buffer.clear();
System.out.println("after reading");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端打印内容:
+--------+-------------------- read -----------------------+----------------+
position: [0], limit: [5]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f |hello |
+--------+-------------------------------------------------+----------------+
copy
after reading
before connecting...
填写客户端代码:
public class NettyClient {
public static void main(String[] args) {
try (SocketChannel socketChannel = SocketChannel.open()) {
// 建立连接
socketChannel.connect(new InetSocketAddress("localhost", 8080));
Scanner scanner = new Scanner(System.in);
System.out.println("请输入发送的内容:");
// 发送内容
String next = scanner.next();
ByteBuffer byteBuffer = ByteBuffer.wrap(next.getBytes());
while (byteBuffer.hasRemaining()) {
socketChannel.write(byteBuffer);
}
// 读取响应
System.out.println("收到服务端响应:");
ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(responseBuffer) != -1) {
if (responseBuffer.position() > 0) {
break;
}
}
// 转为读取模式
responseBuffer.flip();
// 将 requestBuffer 中的数据读取到字节数组,然后打印出来
byte[] content = new byte[responseBuffer.limit()];
responseBuffer.get(content);
System.out.println(new String(content));
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端打印的内容:
Connected to the target VM, address: '127.0.0.1:4832', transport: 'socket'
请输入发送的内容:
hello
收到服务端响应:
Disconnected from the target VM, address: '127.0.0.1:4832', transport: 'socket'